from django.db import models

# ****************************************************************************
# This avoids the frustrating step of having to set up a new admin user every 
# time you re-initialize your database.
from django.conf import settings
from django.contrib.auth import models as auth_models
from django.contrib.auth.management import create_superuser
from django.db.models import signals

# From http://stackoverflow.com/questions/1466827/ --
#
# Prevent interactive question about wanting a superuser created.  (This code
# has to go in this otherwise empty "models" module so that it gets processed by
# the "syncdb" command during database creation.)
signals.post_syncdb.disconnect(
    create_superuser,
    sender=auth_models,
    dispatch_uid='django.contrib.auth.management.create_superuser')


# Create our own test user automatically.

def create_testuser(app, created_models, verbosity, **kwargs):
  if not settings.DEBUG:
    return
  try:
    auth_models.User.objects.get(username='test')
  except auth_models.User.DoesNotExist:
    print '*' * 80
    print 'Creating test user -- login: test, password: test'
    print '*' * 80
    assert auth_models.User.objects.create_superuser('test', 'x@x.com', 'test')
  else:
    print 'Test user already exists.'

signals.post_syncdb.connect(create_testuser,
    sender=auth_models, dispatch_uid='common.models.create_testuser')
# ****************************************************************************

import fca
from StringIO import StringIO
    
class Attribute(models.Model):
    name = models.CharField(max_length=200, unique=True)
    description = models.TextField(blank=True)
    
    def __unicode__(self):
        return self.name
    
class Object(models.Model):
    name = models.CharField(max_length=200, unique=True)
    description = models.TextField(blank=True)
    # We try to avoid using another model to store incidence relation
    # Instead we use special django many-to-many field
    attributes = models.ManyToManyField(Attribute, blank=True)
    OBJECT_TYPES = (
        ("C", "Concept"),
        ("S", "State")
    )
    type = models.CharField(max_length=1, choices=OBJECT_TYPES, default="C")
    
    def __unicode__(self):
        return self.name
    
    def get_as_boolean_list(self):
        """Return a list which represents what attributes this object has"""
        object_intent = self.attributes.all()
        all_attributes = Attribute.objects.all()
        return [(attr in object_intent) for attr in all_attributes]
    
class ObjectImplication(models.Model):
    """Implication on objects"""
    premise = models.ManyToManyField(Object, related_name='premise_set+')
    conclusion = models.ManyToManyField(Object, 
                                        related_name='conclusion_set+')
    minimal_generator = models.ManyToManyField(Object, 
                                               related_name='mingen_set+')
    is_confirmed = models.BooleanField(default=False)
    
    def get_premise(self):
        return set([obj.name for obj in self.premise.all()])
        
    def get_conclusion(self):
        return set([obj.name for obj in self.conclusion.all()])
        
    def equal_to_fca_implication(self, imp):
        """
        Return true if this implication equals to *imp* implication from fca
        package
        """
        return ((self.get_premise() == imp.get_premise()) and
               (self.get_conclusion() == imp.get_conclusion()))
               
    def get_formatted_premise(self):
       return ", ".join(self.get_premise())

    def get_formatted_conclusion(self):
       return ", ".join(self.get_conclusion())
       
    def get_fca_implication(self):
        return fca.Implication(self.get_premise(), self.get_conclusion())  
    
class AttributeImplication(models.Model):
    """Implication on attributes"""
    premise = models.ManyToManyField(Attribute, related_name='premise_set+')
    conclusion = models.ManyToManyField(Attribute, 
                                        related_name='conclusion_set+')
    minimal_generator = models.ManyToManyField(Attribute, 
                                               related_name='mingen_set+')
    is_confirmed = models.BooleanField(default=False)
    
    def get_premise(self):
        return set([attr.name for attr in self.premise.all()])
        
    def get_conclusion(self):
        return set([attr.name for attr in self.conclusion.all()])
    
    def get_fca_implication(self):
        return fca.Implication(self.get_premise(), self.get_conclusion())
        
    def equal_to_fca_implication(self, imp):
        """
        Return true if this implication equals to *imp* implication from fca
        package
        """
        return ((self.get_premise() == imp.get_premise()) and
               (self.get_conclusion() == imp.get_conclusion()))
               
    def get_formatted_premise(self):
        return ", ".join(self.get_premise())
        
    def get_formatted_conclusion(self):
        return ", ".join(self.get_conclusion())
        
    @classmethod
    def create_from_fca_implication(self, fca_imp):
        db_imp = AttributeImplication(is_confirmed=False)
        db_imp.save()
        for name in fca_imp.get_premise():
            db_imp.premise.add(Attribute.objects.get(name=name))
        for name in fca_imp.get_conclusion() - fca_imp.get_premise():
            db_imp.conclusion.add(Attribute.objects.get(name=name))
    
class Event(models.Model):
    """Exploration event. Stores information about any action performed by 
    user"""
    TYPE_CHOICES = (
        ('obj_rem', 'Object removed'),
        ('attr_rem', 'Attribute removed'),
        ('obj_edit', 'Object edited'),
        ('attr_edit', 'Attribute edited'),
        ('obj_add', 'Object added'),
        ('attr_add', 'Attribute added'),
        ('obj_impl_conf', 'Object implication confirmed'),
        ('attr_impl_conf', 'Attribute implication confirmed'),
        ('obj_impl_rej', 'Object implication rejected'),
        ('attr_impl_rej', 'Attribute implication rejected'),
    )
    type = models.CharField(max_length=20, choices=TYPE_CHOICES)
    time = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(auth_models.User)
    annotation = models.TextField(blank=True)
    
    class Meta:
        # We set default ordering from newer to older events
        ordering = ['-time']        

def import_context(file_):
    """Read context from uploaded cxt file and replace current context with it
    """
    input_file = file_
    assert input_file.readline().strip() == "B",\
        "File is not valid cxt"
    input_file.readline() # Empty line
    number_of_objects = int(input_file.readline().strip())
    number_of_attributes = int(input_file.readline().strip())
    input_file.readline() # Empty line

    objects = [input_file.readline().strip() 
               for i in xrange(number_of_objects)]
    attributes = [input_file.readline().strip() 
                  for i in xrange(number_of_attributes)]

    table = []
    for i in xrange(number_of_objects):
        line = map(lambda c: c=="X", input_file.readline().strip())
        table.append(line)

    input_file.close()

    cxt = fca.Context(table, objects, attributes)
    set_context(cxt)
    
def clear_db():
    ObjectImplication.objects.all().delete()
    AttributeImplication.objects.all().delete()
    Object.objects.all().delete()
    Attribute.objects.all().delete()
    Event.objects.all().delete()

def set_context(cxt):
    """Set current context to context from fca.context"""
    clear_db()
    for attribute_name in cxt.attributes:
        attr = Attribute(name=attribute_name)
        attr.save()
    
    for object_name in cxt.objects:
        obj = Object(name=object_name)
        obj.save()
        object_intent = cxt.get_object_intent(object_name)
        for attribute_name in cxt.attributes:
            if attribute_name in object_intent:
                obj.attributes.add(Attribute.objects.get(name=attribute_name))
                     
    flush_implications_basis()
                
def get_context_as_cxt():
    all_objects = Object.objects.all()
    all_attributes = Attribute.objects.all()
    object_names = [obj.name for obj in all_objects]
    attribute_names = [attr.name for attr in all_attributes]
    table = []
    for obj in all_objects:
        table.append(obj.get_as_boolean_list())
    return fca.Context(table, object_names, attribute_names)
    
def get_csv():
    all_objects = Object.objects.all()
    all_attributes = Attribute.objects.all()
    object_names = [obj.name for obj in all_objects]
    attribute_names = [attr.name for attr in all_attributes]
    table = []
    for obj in all_objects:
        table.append(obj.get_as_boolean_list())
    
    out = StringIO()
    q = lambda s: s.replace('\"', '\'')
    print >>out, u";" + u";".join(q(attr) for attr in attribute_names)
    f = lambda n: u"1" if n else u"0"
    for i in xrange(len(object_names)):
        print >>out, u";".join([q(object_names[i])] + [f(n) for n in table[i]])
    return out.getvalue()
    
    
def recompute_basis():
    cxt = get_context_as_cxt()
    ObjectImplication.objects.filter(is_confirmed=False).delete()
    AttributeImplication.objects.filter(is_confirmed=False).delete()
    
    conf_imps = [imp.get_fca_implication() for imp in ObjectImplication.objects.all()]
    object_implications = cxt.get_object_implications(confirmed=conf_imps)
    confirmed_object_implications = ObjectImplication.objects.filter(
                                                        is_confirmed=True
                                                        )
    for imp in object_implications:
        _pass = False
        if _pass:
            continue
        for conf_imp in confirmed_object_implications:
            if conf_imp.equal_to_fca_implication(imp):
                _pass = True
                break
        db_imp = ObjectImplication(is_confirmed=False)
        db_imp.save()
        for name in imp.get_premise():
            db_imp.premise.add(Object.objects.get(name=name))
        for name in imp.get_conclusion() - imp.get_premise():
            db_imp.conclusion.add(Object.objects.get(name=name))
    
    conf_imps = [imp.get_fca_implication() for imp in AttributeImplication.objects.all()]
    attr_implications = cxt.get_attribute_implications(confirmed=conf_imps)
    confirmed_attr_implications = AttributeImplication.objects.filter(
                                                        is_confirmed=True
                                                        )
    for imp in attr_implications:
        _pass = False
        if _pass:
            continue
        for conf_imp in confirmed_attr_implications:
            if conf_imp.equal_to_fca_implication(imp):
                _pass = True
                break
        db_imp = AttributeImplication(is_confirmed=False)
        db_imp.save()
        for name in imp.get_premise():
            db_imp.premise.add(Attribute.objects.get(name=name))
        for name in imp.get_conclusion() - imp.get_premise():
            db_imp.conclusion.add(Attribute.objects.get(name=name))
    
def flush_implications_basis():
    ObjectImplication.objects.all().delete()
    AttributeImplication.objects.all().delete()
    
    cxt = get_context_as_cxt()
    object_implications = cxt.object_implications
    attribute_implications = cxt.attribute_implications
    
    for imp in object_implications:
        db_imp = ObjectImplication(is_confirmed=False)
        db_imp.save()
        for name in imp.get_premise():
            db_imp.premise.add(Object.objects.get(name=name))
        for name in imp.get_conclusion() - imp.get_premise():
            db_imp.conclusion.add(Object.objects.get(name=name))
            
    for imp in attribute_implications:
        db_imp = AttributeImplication(is_confirmed=False)
        db_imp.save()
        for name in imp.get_premise():
            db_imp.premise.add(Attribute.objects.get(name=name))
        for name in imp.get_conclusion() - imp.get_premise():
            db_imp.conclusion.add(Attribute.objects.get(name=name))